package model;

import java.util.Collection;
import java.util.Iterator;


public class Stepper implements Runnable, Observer{
	private final Configuration configuration;
	private final StepperManager stepperManager;
	private boolean stopped;
	/**
	 * Constructor of {@link Stepper}.
	 * @param stepperManager is the {@link StepperManager} of this {@link Stepper}.
	 * @param create is the start {@link Configuration} of this {@link Stepper}.
	 */
	public Stepper(final StepperManager stepperManager, final Configuration create) {
		this.stepperManager = stepperManager;
		this.configuration = create;
		stepperManager.register(this);
	}

	@Override
	public void run() {
		while(!this.stopped){
			this.step();
		}
	}

	/**
	 * Does a single step when an input char is still available.
	 * Sends a {@link NotRecognizedResult} to the {@link StepperManager} when no {@link Transition}s from 
	 * the current {@link State} in the {@link Configuration} with the current input char is in the {@link Automaton} of the {@link StepperManager}.
	 * Sends a {@link RecognizedResult} when no input chars are left in the {@link Configuration}.
	 */
	private void step() {
		if(this.configuration.hasInput()){
			final Collection<Transition> transitions = this.stepperManager.getAutomaton().getAllFromWith(this.configuration.getState(), this.configuration.getNextInput());	
			if(transitions.isEmpty()){
				this.sendResultToManager(NotRecognizedResult.getInstance());
			} else {
				final Iterator<Transition> transitionInterator = transitions.iterator();
				while(transitionInterator.hasNext()){
					final Transition transition = transitionInterator.next();
					final CharOutput nextOutput =  new CharOutput(transition.getOutput(), this.configuration.getOutput());
					
					if(transitionInterator.hasNext()){
						new Thread(new Stepper(this.stepperManager, Configuration.create(transition.getTo(),
																						 this.configuration.getInput(),
																						 this.configuration.getPosition(),
																						 nextOutput ))).start();
					} else {
						this.configuration.setState(transition.getTo());
						this.configuration.setOutput(nextOutput);
					}
				}
			}
		} else {
			this.sendResultToManager(new RecognizedResult(this.configuration.getOutput().getString()));
		}
	}
	
	/**
	 * Sends a result to the {@link StepperManager}.
	 * Stops this {@link Stepper} and deregisters this {@link Stepper} form the {@link StepperManager} before that.
	 * @param result is the {@link Result} that is sent to the {@link StepperManager}.
	 */
	private void sendResultToManager(final Result result){
		this.stopped = true;
		this.stepperManager.deregister(this);
		this.stepperManager.sendResult(result);
	}
	
	/**
	 * Stops this Stepper.
	 */
	@Override
	public void update() {
		this.stopped = true;
	}

}
